/* -*-c++-*- IfcGears - www.ifcgears.com  - Copyright (C) 2011 Fabian Gerold
*
* This library is open source and may be redistributed and/or modified under  
* the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or 
* (at your option) any later version.  The full license is in LICENSE file
* included with this distribution, and on the openscenegraph.org website.
* 
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
* OpenSceneGraph Public License for more details.
*/

#include <cstring>

#include <osg/Vec4f>
#include <osg/Material>
#include <osg/Depth>
#include <osgFX/SpecularHighlights>

#include "ifcgears/IFC2X4/include/IfcSpecularHighlightSelect.h"
#include "ifcgears/IFC2X4/include/IfcSpecularExponent.h"
#include "ifcgears/IFC2X4/include/IfcSpecularRoughness.h"
#include "ifcgears/IFC2X4/include/IfcColourOrFactor.h"
#include "ifcgears/IFC2X4/include/IfcColourRgb.h"
#include "ifcgears/IFC2X4/include/IfcNormalisedRatioMeasure.h"
#include "ifcgears/IFC2X4/include/IfcDraughtingPreDefinedColour.h"
#include "ifcgears/IFC2X4/include/IfcLabel.h"
#include "ifcgears/IFC2X4/include/IfcSurfaceStyleElementSelect.h"
#include "ifcgears/IFC2X4/include/IfcSurfaceStyle.h"
#include "ifcgears/IFC2X4/include/IfcSurfaceStyleShading.h"
#include "ifcgears/IFC2X4/include/IfcSurfaceStyleRendering.h"
#include "ifcgears/IFC2X4/include/IfcStyledItem.h"
#include "ifcgears/IFC2X4/include/IfcPresentationStyleAssignment.h"
#include "ifcgears/IFC2X4/include/IfcPresentationStyleSelect.h"
#include "ifcgears/IFC2X4/include/IfcPresentationStyle.h"
#include "ifcgears/IFC2X4/include/IfcCartesianPoint.h"
#include "ifcgears/IFC2X4/include/IfcProperty.h"
#include "ifcgears/IFC2X4/include/IfcComplexProperty.h"
#include "ifcgears/IFC2X4/include/IfcIdentifier.h"
#include "ifcgears/IFC2X4/include/IfcPropertySingleValue.h"
#include "ifcgears/IFC2X4/include/IfcInteger.h"

#include "StylesConverter.h"

StylesConverter::StylesConverter()
{
}
StylesConverter::~StylesConverter()
{
}

float getShininess(shared_ptr<IfcSpecularHighlightSelect> highlight_select)
{
	float shininess = 64;

	if( dynamic_pointer_cast<IfcSpecularExponent>(highlight_select) )
	{
		shared_ptr<IfcSpecularExponent> spec = dynamic_pointer_cast<IfcSpecularExponent>(highlight_select);
		osg::ref_ptr<osgFX::SpecularHighlights> spec_highlights = new osgFX::SpecularHighlights();
		spec_highlights->setSpecularExponent( spec->m_value );
		// todo: add to scenegraph

	}
	else if( dynamic_pointer_cast<IfcSpecularRoughness>(highlight_select) )
	{
		shared_ptr<IfcSpecularRoughness> specRough = dynamic_pointer_cast<IfcSpecularRoughness>(highlight_select);
		shininess = (float) specRough->m_value*128;
		if (shininess<=1.0) shininess=1.0;
	}
	return shininess;
}

void getColor( shared_ptr<IfcColourOrFactor> colorOrFactor, osg::Vec4f& src_color, osg::Vec4f& target_color)
{
	if( dynamic_pointer_cast<IfcColourRgb>(colorOrFactor) )
	{
		shared_ptr<IfcColourRgb> colorRGB = dynamic_pointer_cast<IfcColourRgb>(colorOrFactor);
		if( colorRGB->m_Red && colorRGB->m_Green && colorRGB->m_Blue )
		{
			target_color.set(  (float) colorRGB->m_Red->m_value,    (float) colorRGB->m_Green->m_value, (float) colorRGB->m_Blue->m_value, 1.f);
		}
	}
	else if( dynamic_pointer_cast<IfcNormalisedRatioMeasure>(colorOrFactor) )
	{
		shared_ptr<IfcNormalisedRatioMeasure> ratio_measure = dynamic_pointer_cast<IfcNormalisedRatioMeasure>(colorOrFactor);
		float factor = ratio_measure->m_value;
		target_color.set( src_color.r()*factor, src_color.g()*factor, src_color.b()*factor, src_color.a() );
	}
}

void getColor( shared_ptr<IfcColourRgb> colorRGB, osg::Vec4f& color )
{
	if( colorRGB->m_Red )
	{
		color._v[0] = (float)colorRGB->m_Red->m_value;
	}
	if( colorRGB->m_Green)
	{
		color._v[1] = (float)colorRGB->m_Green->m_value;
	}
	if( colorRGB->m_Blue)
	{
		color._v[2] = (float)colorRGB->m_Blue->m_value;
	}
}
void getColor( shared_ptr<IfcColour> ifc_color, osg::Vec4f& color )
{
	if( dynamic_pointer_cast<IfcDraughtingPreDefinedColour>(ifc_color) )
	{
		shared_ptr<IfcDraughtingPreDefinedColour> preColor = dynamic_pointer_cast<IfcDraughtingPreDefinedColour>(ifc_color);

		if (preColor->m_Name)
		{
			std::string value = preColor->m_Name->m_value;
			if (value.compare("black")==0)			color.set(0.0f,0.0f,0.0f, 1.f);
			else if (value.compare("red")==0)		color.set(1.0f,0.0f,0.0f, 1.f);
			else if (value.compare("green")==0)		color.set(0.0f,1.0f,0.0f, 1.f);
			else if (value.compare("blue")==0)		color.set(0.0f,0.0f,1.0f, 1.f);
			else if (value.compare("yellow")==0)	color.set(1.0f,1.0f,0.0f, 1.f);
			else if (value.compare("magenta")==0)	color.set(1.0f,0.0f,1.0f, 1.f);
			else if (value.compare("cyan")==0)		color.set(0.0f,1.0f,1.0f, 1.f);
			else if (value.compare("white")==0)		color.set(1.0f,1.0f,1.0f, 1.f);
		}
	}
}

osg::StateSet* StylesConverter::convertIfcSurfaceStyle( shared_ptr<IfcSurfaceStyle> surface_style )
{
	std::vector<shared_ptr<IfcSurfaceStyleElementSelect> >& vec_styles = surface_style->m_Styles;
	std::vector<shared_ptr<IfcSurfaceStyleElementSelect> >::iterator it;

	const int surface_style_id = surface_style->getId();
	std::map<int,osg::ref_ptr<osg::StateSet> >::iterator it_surface_styles = m_map_ifc_surface_styles.find(surface_style_id);
	if( it_surface_styles != m_map_ifc_surface_styles.end() )
	{
		return (*it_surface_styles).second.get();
	}

	osg::StateSet* stateset = NULL;
	
	for( it=vec_styles.begin(); it!=vec_styles.end(); ++it )
	{
		shared_ptr<IfcSurfaceStyleElementSelect> surf_style_element_select = (*it);
		// TYPE IfcSurfaceStyleElementSelect = SELECT	(IfcExternallyDefinedSurfaceStyle	,IfcSurfaceStyleLighting	,IfcSurfaceStyleRefraction	,IfcSurfaceStyleShading	,IfcSurfaceStyleWithTextures);

		shared_ptr<IfcSurfaceStyleShading> surface_style_shading = dynamic_pointer_cast<IfcSurfaceStyleShading>(surf_style_element_select);
		if( surface_style_shading)
		{
			osg::Vec4f color( 0.8f, 0.8f, 0.8f, 1.f );
			if( surface_style_shading->m_SurfaceColour)
			{
				shared_ptr<IfcColourRgb> surf_color = surface_style_shading->m_SurfaceColour;
				getColor( surf_color, color );
			}

			if( color.r() < 0.05 && color.g() < 0.05 && color.b() < 0.05 )
			{
				color.set( 0.9f, 0.9f, 0.9f, color.a() );
			}

			osg::Vec4f ambientColor( color );
			osg::Vec4f emissiveColor( 0.0f, 0.0f, 0.0f, 1.f );
			osg::Vec4f diffuseColor( color );
			osg::Vec4f specularColor( color );
			float shininess = 35.f;
			float transparency = 0.7f;
			bool set_transparent = false;

			shared_ptr<IfcSurfaceStyleRendering> surf_style_rendering = dynamic_pointer_cast<IfcSurfaceStyleRendering>(surf_style_element_select);
			if( surf_style_rendering )
			{
				if( surf_style_rendering->m_DiffuseColour)
				{
					shared_ptr<IfcColourOrFactor> color_or_factor = surf_style_rendering->m_DiffuseColour;
					getColor(color_or_factor, color, diffuseColor);

				}

				if( surf_style_rendering->m_SpecularColour)
				{
					shared_ptr<IfcColourOrFactor> color_or_factor = surf_style_rendering->m_SpecularColour;
					//getColor(color_or_factor, color, specularColor);
				}

				if( surf_style_rendering->m_Transparency)
				{
					// in IFC 1 is transparent, 0 is opaque. if not given, the value 0 (opaque) is assumed
					// in osg, 1 is opaque, 0 is transparent
					transparency = 1.f - (float)surf_style_rendering->m_Transparency->m_value;
					if( transparency < 0.08f )
					{
						transparency = 0.08f;
					}
					
					if( transparency > 1.f )
					{
						transparency = 1.f;
					}

					if( transparency < 0.99f )
					{
						set_transparent = true;
					}
				}

				if( surf_style_rendering->m_SpecularHighlight)
				{
					shared_ptr<IfcSpecularHighlightSelect> spec_highlight = surf_style_rendering->m_SpecularHighlight;
					shininess = getShininess( spec_highlight );
				}
			}
			ambientColor *= 0.8f;
			

			ambientColor._v[3] = transparency;
			diffuseColor._v[3] = transparency;
			specularColor._v[3] = transparency;
			emissiveColor._v[3] = transparency;
			
			osg::ref_ptr<osg::Material> mat = new osg::Material();
			mat->setAmbient( osg::Material::FRONT_AND_BACK, ambientColor );
			mat->setDiffuse( osg::Material::FRONT_AND_BACK, diffuseColor );
			mat->setSpecular( osg::Material::FRONT_AND_BACK, specularColor );
			mat->setShininess( osg::Material::FRONT_AND_BACK, shininess );
			mat->setColorMode( osg::Material::SPECULAR );
			mat->setEmission( osg::Material::FRONT_AND_BACK, emissiveColor );

			if( stateset == NULL )
			{
				stateset = new osg::StateSet();
				m_map_ifc_surface_styles[surface_style_id] = stateset;
			}
			stateset->setAttribute( mat, osg::StateAttribute::ON );
			
			if( set_transparent )
			{
				mat->setTransparency( osg::Material::FRONT_AND_BACK, transparency );	
				stateset->setMode( GL_BLEND, osg::StateAttribute::ON );
				stateset->setAttribute(new osg::Depth( osg::Depth::LESS, 0.0, 1.0, false));
				stateset->setRenderingHint( osg::StateSet::TRANSPARENT_BIN );
			}
			else
			{
				// blending is disabled in this case, so set transparency in case blending is enabled at the root node
				mat->setTransparency( osg::Material::FRONT_AND_BACK, 0.7f );
			}
			continue;
		}
	}
	return stateset;
}

osg::StateSet* StylesConverter::convertIfcStyledItem(weak_ptr<IfcStyledItem> styled_item_weak )
{
	shared_ptr<IfcStyledItem> styled_item( styled_item_weak );
	const int styled_item_id = styled_item->getId();

	std::map<int,osg::ref_ptr<osg::StateSet> >::iterator it_styled_items = m_map_ifc_styled_items.find(styled_item_id);
	if( it_styled_items != m_map_ifc_styled_items.end() )
	{
		// use existing stateset
		return (*it_styled_items).second.get();
	}
	osg::StateSet* resulting_stateset = NULL;

	std::vector<shared_ptr<IfcStyleAssignmentSelect> >& vec_style_assigns = styled_item->m_Styles;
	std::vector<shared_ptr<IfcStyleAssignmentSelect> >::iterator it_style_assigns;
	
	for( it_style_assigns=vec_style_assigns.begin(); it_style_assigns!=vec_style_assigns.end(); ++it_style_assigns )
	{
		shared_ptr<IfcStyleAssignmentSelect> style_assign_select = (*it_style_assigns);
		shared_ptr<IfcPresentationStyleAssignment> style_assign = dynamic_pointer_cast<IfcPresentationStyleAssignment>(style_assign_select);    
		if( style_assign )
		{
			std::vector<shared_ptr<IfcPresentationStyleSelect> >& vec_styles = style_assign->m_Styles;
			//applyStyles( vec_styles, stateset );

			std::vector<shared_ptr<IfcPresentationStyleSelect> >::iterator it_presentation_styles;
			for( it_presentation_styles=vec_styles.begin(); it_presentation_styles!=vec_styles.end(); ++it_presentation_styles )
			{
				shared_ptr<IfcPresentationStyleSelect> pres_style_select = (*it_presentation_styles);

				if( dynamic_pointer_cast<IfcSurfaceStyle>(pres_style_select) )
				{
					shared_ptr<IfcSurfaceStyle> surf_style = dynamic_pointer_cast<IfcSurfaceStyle>(pres_style_select);
					osg::StateSet* surface_style_stateset = convertIfcSurfaceStyle( surf_style );
					if( surface_style_stateset != NULL )
					{
						if( resulting_stateset == NULL )
						{
							resulting_stateset = surface_style_stateset;
						}
						else
						{
							resulting_stateset->merge( *surface_style_stateset );
						}

					}
					break;
				}
			}
		}
		else if( dynamic_pointer_cast<IfcPresentationStyle>(style_assign_select) )
		{
			shared_ptr<IfcPresentationStyle> rep_style = dynamic_pointer_cast<IfcPresentationStyle>(style_assign_select);
			// applyStyles();
		}
	}
	return resulting_stateset;
}

osg::StateSet* StylesConverter::convertIfcComplexPropertyColor( shared_ptr<IfcComplexProperty> complex_property )
{
	std::vector<shared_ptr<IfcProperty> >& vec_HasProperties = complex_property->m_HasProperties;
	if( !complex_property->m_UsageName ) return NULL;
	if( vec_HasProperties.size() < 3 ) return NULL;
	std::string usage_name = complex_property->m_UsageName->m_value;
	if( _stricmp( usage_name.c_str(), "Color" ) != 0 ) return NULL;
	
	int property_set_id = complex_property->getId();
	if( m_map_ifc_complex_property_color.find(property_set_id) != m_map_ifc_complex_property_color.end() )
	{
		// use existing stateset
		return m_map_ifc_complex_property_color[property_set_id];
	}

	shared_ptr<IfcPropertySingleValue> prop1 = dynamic_pointer_cast<IfcPropertySingleValue>(complex_property->m_HasProperties[0]);
	shared_ptr<IfcPropertySingleValue> prop2 = dynamic_pointer_cast<IfcPropertySingleValue>(complex_property->m_HasProperties[1]);
	shared_ptr<IfcPropertySingleValue> prop3 = dynamic_pointer_cast<IfcPropertySingleValue>(complex_property->m_HasProperties[2]);
	if( prop1 && prop2 && prop3 )
	{
		if( prop1->m_NominalValue && prop2->m_NominalValue && prop3->m_NominalValue )
		{
			shared_ptr<IfcInteger> prop1v = dynamic_pointer_cast<IfcInteger>(prop1->m_NominalValue);
			shared_ptr<IfcInteger> prop2v = dynamic_pointer_cast<IfcInteger>(prop2->m_NominalValue);
			shared_ptr<IfcInteger> prop3v = dynamic_pointer_cast<IfcInteger>(prop3->m_NominalValue);

			if( prop1v && prop2v && prop3v )
			{
				double r = prop1v->m_value/255.0;
				double g = prop2v->m_value/255.0;
				double b = prop3v->m_value/255.0;

#ifdef REUSE_EXISTING_STATESETS_SAME_COLOR
				// if there is a stateset with the same colors, use that one
				std::map<int,osg::ref_ptr<osg::StateSet> >::iterator it = m_map_ifc_complex_property_color.begin();
				for( ; it!=m_map_ifc_complex_property_color.end(); ++it )
				{
					osg::StateSet* existing_stateset = (*it).second.get();
					osg::StateAttribute* state_att = existing_stateset->getAttribute( osg::StateAttribute::MATERIAL );
					osg::Material* existing_material = dynamic_cast<osg::Material*>(state_att);
					if( existing_material )
					{
						osg::Vec4f color = existing_material->getDiffuse( osg::Material::FRONT );
						if( fabs(color.r()-r) > 0.04 )
						{
							continue;
						}
						if( fabs(color.g()-g) > 0.04 )
						{
							continue;
						}
						if( fabs(color.b()-b) > 0.04 )
						{
							continue;
						}

						return existing_stateset;
					}
				}
#endif

				if( r < 0.05 && g < 0.05 && b < 0.05 )
				{
					r = 0.9;
					g = 0.9;
					b = 0.9;
				}

				osg::Vec4f color( r, g, b, 1.f );
				osg::Material* mat = new osg::Material();
				mat->setAmbient( osg::Material::FRONT_AND_BACK, color );
				mat->setDiffuse( osg::Material::FRONT_AND_BACK, color );
				mat->setSpecular( osg::Material::FRONT_AND_BACK, color );
				mat->setShininess( osg::Material::FRONT_AND_BACK, 35.f );
				mat->setTransparency( osg::Material::FRONT_AND_BACK, 0.7f );
				mat->setColorMode( osg::Material::SPECULAR );

				osg::StateSet* stateset = new osg::StateSet();
				stateset->setAttribute( mat, osg::StateAttribute::ON );

				m_map_ifc_complex_property_color[property_set_id] = stateset;

				return stateset;
			}
		}
	}
	return NULL;
}
